import binascii
import eth_abi
from http import client as hc
import time
import json
from random import randrange
from sha3 import keccak_256
from transaction.transaction import Transaction, UnverifiedTransaction, VerifiedTransaction

RANDOM_MAX = 65536
JSONRPC_VERSION = "2.0"
HEADERS = {'Content-type': 'application/json'}

def http_post(url: str, method: str, params: list) -> dict:
    id = randrange(RANDOM_MAX)
    conn = hc.HTTPConnection(url)
    data = {
        "jsonrpc": JSONRPC_VERSION, 
        "method": method, 
        "params": params,
        "id": id,
    }
    json_data = json.dumps(data)
    # print("json data:\n{}".format(json_data))
    conn.request("POST", "/", json_data, HEADERS)
    resp = conn.getresponse()
    # print("response:\n{}".format(json_data))
    decoded = resp.read().decode()
    conn.close()
    return json.loads(decoded)

def brochain_get_blocknumber(url: str) -> int:
    ret = http_post(url, 'eth_blockNumber', [])
    # print("type of return: {}".format(type(ret)))
    # print("get_blocknumber return: {}".format(ret))
    return int(ret["result"], 16)

def construct_data(abi: dict, params: list):
    func_name = abi["name"]
    input_types = [inp["internalType"] for inp in abi["inputs"]]
    func_sigature = "{}({})".format(func_name, ",".join(input_types))
    # print("function signature: {}".format(func_sigature))
    kk = keccak_256()
    kk.update(bytes(func_sigature, 'utf-8'))
    signature_hash = kk.hexdigest()
    # print("signature_hash: {}".format(signature_hash))

    code = eth_abi.encode(input_types, params)
    data = "{}{}".format(signature_hash[:8], code.hex())
    return data

def brochain_call(url: str,
                region_id: str,
                from_addr: str,
                to_addr: str,
                abi: dict,
                params: list) -> dict:

    data = '0x' + construct_data(abi, params)
    
    txobj = {
        "to": to_addr,
        "data": data,
    }
    if from_addr:
        txobj["from"] = from_addr
    ret = http_post(url, 'brochain_call', [txobj, region_id, "latest"])
    
    output_types = [out["internalType"] for out in abi["outputs"]]
    output = eth_abi.decode(output_types, bytes.fromhex(ret["result"][2:]))

    return output[0]

def strip_0x(s: str):
    if s[0:2] == '0x':
        return s[2:]
    return s

def construct_param0(region_id: str, 
                    contract_address: str, 
                    blocknumber: int,
                    data: str,
                    private_key: str) -> str:
    region_id = strip_0x(region_id)
    contract_address = strip_0x(contract_address)
    data = strip_0x(data)
    private_key = strip_0x(private_key)

    tx = Transaction(
        region_id = bytes(region_id, 'utf-8'),
        nonce = randrange(RANDOM_MAX), # nonce
        block_limit = blocknumber + 100,
        target = bytes(contract_address, 'utf-8'),
        data = bytes('0x' + data, 'utf-8'),
    )

    print("base transaction:\n{}".format(tx))

    utx = UnverifiedTransaction(
        region_id = tx.region_id,
        nonce = tx.nonce,
        block_limit = tx.block_limit,
        target = tx.target,
        data = tx.data,
    )
    vtx = utx.sign(private_key)
    rlp_encoded = vtx.rlp_encode()
    print(utx)
    print(vtx)
    print("rpl_encoded(type: {}):\n{}".format(type(rlp_encoded), rlp_encoded))
    return rlp_encoded

def brochain_get_tx_by_hash(url: str,
                            region_id: str,
                            tx_hash: str) -> dict:
    ret = http_post(url, 'brochain2_getTransactionReceiptByHash', [region_id, tx_hash])
    return ret["result"]

def brochain_send_tx(url: str,
                    region_id: str,
                    contract_address: str,
                    abi: str,
                    params: list,
                    key: str):

    data = construct_data(abi, params)
    bn = brochain_get_blocknumber(url)
    param0 = '0x' + binascii.b2a_hex(construct_param0(region_id, contract_address, bn, data, key)).decode('utf-8')
    print("param0:\n{}".format(param0))
    ret = http_post(url, 'brochain_sendRawTransaction', [param0])
    print("brochain_sendRawTransaction result:\n{}".format(ret))
    tx_hash = ret["result"]

    for i in range(5):
        time.sleep(1 + i)
        ret = brochain_get_tx_by_hash(url, region_id, tx_hash)
        if result:
            return result
